﻿Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime
Imports System.Workflow.Runtime.Tracking

Namespace System.Workflow.Activities
    Public NotInheritable Class StateMachineWorkflowInstance
        ' Methods
        Public Sub New(ByVal runtime As WorkflowRuntime, ByVal instanceId As Guid)
            If (runtime Is Nothing) Then
                Throw New ArgumentNullException("runtime")
            End If
            If (instanceId = Guid.Empty) Then
                Throw New ArgumentNullException("instanceId")
            End If
            Me._runtime = runtime
            Me._instanceId = instanceId
            Me._workflowInstance = runtime.GetWorkflow(instanceId)
            Me._stateMachineWorkflow = [TryCast](Me._workflowInstance.GetWorkflowDefinition,StateMachineWorkflowActivity)
            If (Me._stateMachineWorkflow Is Nothing) Then
                Throw New ArgumentException(SR.GetStateMachineWorkflowRequired, "instanceId")
            End If
        End Sub

        Public Sub EnqueueItem(ByVal queueName As IComparable, ByVal item As Object)
            Me.EnqueueItem(queueName, item, Nothing, Nothing)
        End Sub

        Public Sub EnqueueItem(ByVal queueName As IComparable, ByVal item As Object, ByVal pendingWork As IPendingWork, ByVal workItem As Object)
            Me.WorkflowInstance.EnqueueItemOnIdle(queueName, item, pendingWork, workItem)
        End Sub

        Friend Function FindActivityByQualifiedName(ByVal id As String) As Activity
            Return StateMachineHelpers.FindActivityByName(Me.StateMachineWorkflow, id)
        End Function

        Private Function GetCurrentState() As StateActivity
            Dim info As WorkflowQueueInfo
            For Each info In Me.WorkflowInstance.GetWorkflowQueueData
                If info.QueueName.Equals("SetStateQueue") Then
                    If (info.SubscribedActivityNames.Count = 0) Then
                        Return Nothing
                    End If
                    Return StateMachineHelpers.FindStateByName(Me.StateMachineWorkflow, info.SubscribedActivityNames.Item(0))
                End If
            Next
            Return Nothing
        End Function

        Private Shared Function GetLeafStates(ByVal parentState As StateActivity) As ReadOnlyCollection([Of] StateActivity)
            If (parentState Is Nothing) Then
                Throw New ArgumentNullException("parentState")
            End If
            Dim list As New List([Of] StateActivity)
            Dim queue As New Queue([Of] StateActivity)
            queue.Enqueue(parentState)
            Do While (queue.Count > 0)
                Dim activity2 As Activity
                For Each activity2 In queue.Dequeue.EnabledActivities
                    Dim state As StateActivity = [TryCast](activity2,StateActivity)
                    If (Not state Is Nothing) Then
                        If StateMachineHelpers.IsLeafState(state) Then
                            list.Add(state)
                        Else
                            queue.Enqueue(state)
                        End If
                    End If
                Next
            Loop
            Return list.AsReadOnly
        End Function

        Private Function GetPossibleStateTransitions() As ReadOnlyCollection([Of] String)
            Dim list As New List([Of] String)
            Dim workflowQueueData As ReadOnlyCollection([Of] WorkflowQueueInfo) = Me.WorkflowInstance.GetWorkflowQueueData
            Dim stateMachineWorkflow As StateMachineWorkflowActivity = Me.StateMachineWorkflow
            Dim info As WorkflowQueueInfo
            For Each info In workflowQueueData
                Dim str As String
                For Each str In info.SubscribedActivityNames
                    Dim eventActivity As IEventActivity = [TryCast](StateMachineHelpers.FindActivityByName(stateMachineWorkflow, str),IEventActivity)
                    If (Not eventActivity Is Nothing) Then
                        Dim parentEventDriven As EventDrivenActivity = StateMachineHelpers.GetParentEventDriven(eventActivity)
                        Dim queue As New Queue([Of] Activity)
                        queue.Enqueue(parentEventDriven)
                        Do While (queue.Count > 0)
                            Dim activity5 As Activity = queue.Dequeue
                            Dim activity6 As SetStateActivity = [TryCast](activity5,SetStateActivity)
                            If (Not activity6 Is Nothing) Then
                                list.Add(activity6.TargetStateName)
                            Else
                                Dim activity7 As CompositeActivity = [TryCast](activity5,CompositeActivity)
                                If (Not activity7 Is Nothing) Then
                                    Dim activity8 As Activity
                                    For Each activity8 In activity7.EnabledActivities
                                        queue.Enqueue(activity8)
                                    Next
                                    [Continue] Do
                                End If
                            End If
                        Loop
                    End If
                Next
            Next
            Return list.AsReadOnly
        End Function

        Private Function GetStateHistory() As ReadOnlyCollection([Of] String)
            Dim stateMachineWorkflow As StateMachineWorkflowActivity
            If (Me._sqlTrackingService Is Nothing) Then
                Me._sqlTrackingService = Me._runtime.GetService([Of] SqlTrackingService)
                If (Me._sqlTrackingService Is Nothing) Then
                    Throw New InvalidOperationException(SR.GetSqlTrackingServiceRequired)
                End If
            End If
            If (Me._sqlTrackingQuery Is Nothing) Then
                Me._sqlTrackingQuery = New SqlTrackingQuery(Me._sqlTrackingService.ConnectionString)
            End If
            Dim stack As New Stack([Of] String)
            Try 
                stateMachineWorkflow = Me.StateMachineWorkflow
            Catch exception1 As InvalidOperationException
                Return New ReadOnlyCollection([Of] String)(stack.ToArray)
            End Try
            If ((Not Me._sqlTrackingWorkflowInstance Is Nothing) OrElse Me._sqlTrackingQuery.TryGetWorkflow(Me._instanceId, Me._sqlTrackingWorkflowInstance)) Then
                Me._sqlTrackingWorkflowInstance.Refresh
                Dim record As UserTrackingRecord
                For Each record In Me._sqlTrackingWorkflowInstance.UserEvents
                    If (record.UserDataKey = "StateActivity.StateChange") Then
                        Dim userData As String = [TryCast](record.UserData,String)
                        If (userData Is Nothing) Then
                            Throw New InvalidOperationException(SR.GetInvalidUserDataInStateChangeTrackingRecord)
                        End If
                        Dim state As StateActivity = StateMachineHelpers.FindStateByName(stateMachineWorkflow, record.QualifiedName)
                        If (state Is Nothing) Then
                            Throw New InvalidOperationException(SR.GetInvalidUserDataInStateChangeTrackingRecord)
                        End If
                        If StateMachineHelpers.IsLeafState(state) Then
                            stack.Push(userData)
                        End If
                    End If
                Next
            End If
            Return New ReadOnlyCollection([Of] String)(stack.ToArray)
        End Function

        Public Sub SetState(ByVal targetStateName As String)
            If (targetStateName Is Nothing) Then
                Throw New ArgumentNullException("targetStateName")
            End If
            If Not TypeOf Me.FindActivityByQualifiedName(targetStateName) Is StateActivity Then
                Throw New ArgumentOutOfRangeException("targetStateName")
            End If
            Dim item As New SetStateEventArgs(targetStateName)
            Me.WorkflowInstance.EnqueueItemOnIdle("SetStateQueue", item, Nothing, Nothing)
        End Sub

        Public Sub SetState(ByVal targetState As StateActivity)
            If (targetState Is Nothing) Then
                Throw New ArgumentNullException("targetState")
            End If
            Me.SetState(targetState.QualifiedName)
        End Sub


        ' Properties
        Public ReadOnly Property CurrentState As StateActivity
            Get
                Return Me.GetCurrentState
            End Get
        End Property

        Public ReadOnly Property CurrentStateName As String
            Get
                Dim currentState As StateActivity = Me.CurrentState
                If (currentState Is Nothing) Then
                    Return Nothing
                End If
                Return currentState.QualifiedName
            End Get
        End Property

        Public ReadOnly Property InstanceId As Guid
            Get
                Return Me._instanceId
            End Get
        End Property

        Public ReadOnly Property PossibleStateTransitions As ReadOnlyCollection([Of] String)
            Get
                Return Me.GetPossibleStateTransitions
            End Get
        End Property

        Public ReadOnly Property StateHistory As ReadOnlyCollection([Of] String)
            Get
                Return Me.GetStateHistory
            End Get
        End Property

        Public ReadOnly Property StateMachineWorkflow As StateMachineWorkflowActivity
            Get
                Try 
                    Me._stateMachineWorkflow = DirectCast(Me.WorkflowInstance.GetWorkflowDefinition, StateMachineWorkflowActivity)
                Catch exception1 As InvalidOperationException
                End Try
                Return Me._stateMachineWorkflow
            End Get
        End Property

        Public ReadOnly Property States As ReadOnlyCollection([Of] StateActivity)
            Get
                Dim stateMachineWorkflow As StateMachineWorkflowActivity = Me.StateMachineWorkflow
                If (stateMachineWorkflow Is Nothing) Then
                    Throw New InvalidOperationException
                End If
                Return StateMachineWorkflowInstance.GetLeafStates(stateMachineWorkflow)
            End Get
        End Property

        Public ReadOnly Property WorkflowInstance As WorkflowInstance
            Get
                Return Me._workflowInstance
            End Get
        End Property


        ' Fields
        Private _instanceId As Guid
        Private _runtime As WorkflowRuntime
        Private _sqlTrackingQuery As SqlTrackingQuery
        Private _sqlTrackingService As SqlTrackingService
        Private _sqlTrackingWorkflowInstance As SqlTrackingWorkflowInstance
        Private _stateMachineWorkflow As StateMachineWorkflowActivity
        Private _workflowInstance As WorkflowInstance
        Friend Const StateHistoryPropertyName As String = "StateHistory"
    End Class
End Namespace

